Optimising Parking Accessibility Near Healthcare and Educational Facilities in Melbourne
Authored by: Nattakan Owatwansakul
Duration: 90 mins
Level: Intermediate
Pre-requisite Skills: Python, Data cleaning, Data analysis, Data visualisation
Scenario

As an urban planner in the city of Melbourne, I want to identify areas near healthcare and educational facilities where parking is limited but demand is high, So that I can make informed decisions about improving parking infrastructure and accessibility for patients, students, and staff.

What this use case will teach you

At the end of this use case you will:

  • Learn how to import and combine data from various sources.
  • Understand relationship between parking demand, pedestrian traffic and public facilities.
  • Gain skills in analysing data and presenting data insights for clients
Introduction

In growing cities like Melbourne, healthcare and educational facilities are essential parts of the community and attract high numbers of daily visitors. These places often have a lot of foot traffic, especially during rush hours. However, there is not always enough parking nearby for patients, students, and staff. This can cause inconvinience, delays, and access issues, especially for patients, students, and people who need these services.

This use case aims to explore parking accessibility around healthcare and educational institutions in Melbourne. By analysing data on car park capacity, facility locations, and pedestrian movement, the project seeks to identify areas with high demand but limited parking. The results will support urban planners in making informed decisions to improve parking infrastructure and future city planning.

Datasets
  • Off-street car parks with capacity and type
    Data collected as part of the City of Melbourne's Census of Land Use and Employment (CLUE). The data covers the period 2002-2023.
  • Landmarks and places of interest, including schools, theatres, health services, sports facilities, places of worship, galleries and museums
    Dataset contains a description and co-ordinates of places of interest within the City of Melbourne.
  • Pedestrian Counting System (counts per hour)

This dataset contains hourly pedestrian counts since 2009 from pedestrian sensor devices located across the city. The data is updated on a monthly basis and can be used to determine variations in pedestrian activity throughout the day.

  • Bus stops

This data set shows the locations of the bus stops within the city of Melbourne, As the city of Melbourne do not run the bus services, this is simply to show the locations of the stops, this data does not include the services that run from each of the stops.

  • Tram Tracks

This dataset is a shapefile that contains all of the tram tracks within the city of Melbourne, this data set is generally used for City of Melbourne related queries, therefor we do not hold the entire tram network

Import Libraries¶

In [11]:
#for using APIs
import requests
#data monipulation and analysis
import pandas as pd
# convert text
from io import StringIO
import math
#calculate distance between two points
from geopy.distance import geodesic
#interactive map
import folium
from collections import Counter
from branca.element import Template, MacroElement
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

Loading data through APIs V2.1 from CoM open data portal¶

We define a function that connects to the City of Melbourne’s Open Data platform to automatically retrieve datasets relevant to this project. Rather than downloading files manually, this function sends a request to the data portal through a web address (API) and asks for the entire dataset. The data is then converted into a format suitable for analysis in Python, using pandas.

This method ensures we are working with the most current and complete data available. In this use case, we use it to load datasets such as landmarks (including schools, libraries, and health services), parking availability, and pedestrian traffic. This approach improves efficiency, keeps the project up to date, and reduces the risk of human error associated with manual downloads.

In [14]:
def API_Unlimited(dataset_id):
    base_url = 'https://data.melbourne.vic.gov.au/api/explore/v2.1/catalog/datasets/'
    #apikey = 'apikey
    dataset_id = dataset_id
    format = 'csv'

    url = f'{base_url}{dataset_id}/exports/{format}'
    params = {
        'select': '*',
        'limit': -1,
        'lang': 'en',
        'timezone': 'UTC',
    }
    response = requests.get(url, params=params)

    if response.status_code == 200:
        url_content = response.content.decode('utf-8')
        dataset = pd.read_csv(StringIO(url_content), delimiter=';')
        return dataset
    else:
        print(f'Request failed with status code {response.status_code}')
In [80]:
landmark_location = API_Unlimited('landmarks-and-places-of-interest-including-schools-theatres-health-services-spor')
parking_capacity = API_Unlimited('off-street-car-parks-with-capacity-and-type')
pedestrian_count = API_Unlimited('pedestrian-counting-system-monthly-counts-per-hour')
bus_stop = API_Unlimited('bus-stops')
tram_stop = API_Unlimited('tram-tracks')
In [81]:
bus_stop = API_Unlimited('bus-stops')
tram_stop = API_Unlimited('tram-tracks')

Previewing the Raw Datasets¶

  1. The landmark dataset contains important public locations across Melbourne, such as schools, health services, and places of worship. It includes columns like theme, sub_theme, feature_name, and co_ordinates (latitude and longitude combined). This information helps us later identify which landmarks are near public parking or pedestrian sensors.
In [83]:
landmark_location.head()
Out[83]:
theme sub_theme feature_name co_ordinates
0 Place of Worship Church St Francis Church -37.8118847831837, 144.962422614541
1 Place of Worship Church St James Church -37.8101281201969, 144.952468571683
2 Place of Worship Church St Mary's Anglican Church -37.8031663672997, 144.953761537074
3 Place of Worship Church Scots Church -37.8145687802664, 144.96855105335
4 Place of Worship Church St Michael's Uniting Church -37.8143851324913, 144.969174036096
  1. The parking capacity dataset provides details about off-street parking facilities, such as the census_year, block_id, property_id, base_property_id, building_address, number of parking_spaces, and parking_type (e.g., Public, Private, or Residential). It also contains location data in separate latitude and longitude columns. However, this dataset includes private and residential car parks, which are not publicly available. We will filter those out in later steps to focus only on publicly usable parking spaces.
In [85]:
parking_capacity.head()
Out[85]:
census_year block_id property_id base_property_id building_address clue_small_area parking_type parking_spaces longitude latitude location
0 2019 57 101213 101213 134-144 Bourke Street MELBOURNE 3000 Melbourne (CBD) Private 30 144.968839 -37.812035 -37.81203485275635, 144.9688385527
1 2019 57 101219 101219 100-116 Bourke Street MELBOURNE 3000 Melbourne (CBD) Private 68 144.969761 -37.811727 -37.81172679558099, 144.96976088474997
2 2019 58 101231 101231 40-50 Bourke Street MELBOURNE 3000 Melbourne (CBD) Residential 26 144.971692 -37.811371 -37.81137105206584, 144.9716919528
3 2019 58 101232 101232 32-38 Bourke Street MELBOURNE 3000 Melbourne (CBD) Private 5 144.971978 -37.811361 -37.81136084732718, 144.97197812345
4 2019 58 105659 105659 16-22 Liverpool Street MELBOURNE 3000 Melbourne (CBD) Residential 85 144.971543 -37.811060 -37.81105967715, 144.97154296349532
  1. The pedestrian count dataset records hourly foot traffic from sensors placed around the city. Each row includes the sensor location_id, date and hour of measurement (sensing_date, hourday), the number of people counted in each direction, and the total pedestriancount. The location column provides the coordinates of each sensor, which we will use to match with landmarks later.
In [87]:
pedestrian_count.head()
Out[87]:
id location_id sensing_date hourday direction_1 direction_2 pedestriancount sensor_name location
0 72120220515 72 2022-05-15 1 104 172 276 ACMI_T -37.81726338, 144.96872809
1 471720240917 47 2024-09-17 17 1273 894 2167 Eli250_T -37.81258467, 144.9625775
2 172320211101 17 2021-11-01 23 8 6 14 Col15_T -37.81362543, 144.97323591
3 171820230726 17 2023-07-26 18 267 383 650 Col15_T -37.81362543, 144.97323591
4 24820250405 24 2025-04-05 8 213 218 431 Col620_T -37.81887963, 144.95449198
  1. The bus stops dataset shows the locations of bus stop signage across Melbourne. Each row includes a geo_point_2d column with the coordinates (latitude and longitude) and a description column that describes the bus stop type. Other attributes such as roadseg_id and model_no provide additional details about the stop. We will use the coordinate data to find bus stops near healthcare and educational facilities. This helps us check public transport accessibility, which may reduce parking demand if many people can reach the area by bus.
In [89]:
bus_stop.head()
Out[89]:
geo_point_2d geo_shape prop_id addresspt1 addressp_1 asset_clas asset_type objectid str_id addresspt asset_subt model_desc mcc_id roadseg_id descriptio model_no
0 -37.80384165792465, 144.93239283833262 {"coordinates": [144.93239283833262, -37.80384... 0 76.819824 357 Signage Sign - Public Transport 355 1235255 570648 NaN Sign - Public Transport 1 Panel 1235255 21673 Sign - Public Transport 1 Panel Bus Stop Type 13 P.16
1 -37.81548699581418, 144.9581794249902 {"coordinates": [144.9581794249902, -37.815486... 0 21.561304 83 Signage Sign - Public Transport 600 1231226 548056 NaN Sign - Public Transport 1 Panel 1231226 20184 Sign - Public Transport 1 Panel Bus Stop Type 8 P.16
2 -37.81353897396532, 144.95728334230756 {"coordinates": [144.95728334230756, -37.81353... 0 42.177187 207 Signage Sign - Public Transport 640 1237092 543382 NaN Sign - Public Transport 1 Panel 1237092 20186 Sign - Public Transport 1 Panel Bus Stop Type 8 P.16
3 -37.82191394843844, 144.95539345270072 {"coordinates": [144.95539345270072, -37.82191... 0 15.860434 181 Signage Sign - Public Transport 918 1232777 103975 NaN Sign - Public Transport 1 Panel 1232777 22174 Sign - Public Transport 1 Panel Bus Stop Type 8 P.16
4 -37.83316401267591, 144.97443745130263 {"coordinates": [144.97443745130263, -37.83316... 0 0.000000 0 Signage Sign - Public Transport 1029 1271914 0 NaN Sign - Public Transport 1 Panel 1271914 22708 Sign - Public Transport 1 Panel Bus Stop Type 8 P.16
  1. The tram tracks dataset shows the paths where tram lines are located across Melbourne. Each row includes geo_point_2d, which gives the approximate location (latitude and longitude), and geo_shape, which provides the full shape of the tram track route using multiple coordinates. Other columns like description and name provide simple identifiers for the tram routes. We will use this data to map tram lines and see how close they are to important landmarks. This helps assess tram accessibility and how it may influence parking needs in each area.
In [91]:
tram_stop.head()
Out[91]:
geo_point_2d geo_shape descriptio name
0 -37.78861331868605, 144.93461561782556 {"coordinates": [[[[144.934525400489, -37.7886... <center><table><tr><th colspan='2' align='cent... kml_3
1 -37.819185576253524, 144.9610352542915 {"coordinates": [[[[144.96099441333, -37.81917... <center><table><tr><th colspan='2' align='cent... kml_5
2 -37.81837971233182, 144.95945259116502 {"coordinates": [[[[144.959343869512, -37.8182... <center><table><tr><th colspan='2' align='cent... kml_6
3 -37.81440385390417, 144.9702510191494 {"coordinates": [[[[144.969150077924, -37.8147... <center><table><tr><th colspan='2' align='cent... kml_7
4 -37.816738613153454, 144.9699088149105 {"coordinates": [[[[144.970082905454, -37.8167... <center><table><tr><th colspan='2' align='cent... kml_8

Data Preprocessing¶

Landmarks and places of interest, including schools, theatres, health services, sports facilities, places of worship, galleries and museums¶

This dataset includes a wide range of place types, such as schools, theatres, health services, sports facilities, galleries and more. For this project, we filtered the data to include only Health Services and Education Centres, as these locations are our primary focus for improving parking accessibility. These sites deliver essential public services and often experience high visitor volumes. We also included libraries (filtered from the “Community Use” category)

In [94]:
def filter_landmarks(data):
    #choose only heath services and edication centre theme
    themes = ["Health Services", "Education Centre"]
    filtered_theme = data[data['theme'].isin(themes)]

    #from 'Community Use' theme, include only libraries
    community_use = data[
        (data['theme'] == "Community Use") &
        (data['feature_name'].str.contains("library", case=False, na=False))
    ]

    filtered_landmarks = pd.concat([filtered_theme, community_use], ignore_index=True)
    return filtered_landmarks
In [95]:
filtered_landmarks = filter_landmarks(landmark_location)

#split 'co_ordinates' column into 'latitude' and 'longitude' columns
filtered_landmarks[['latitude', 'longitude']] = filtered_landmarks['co_ordinates'].str.split(',', expand=True)

#change string to float
filtered_landmarks['latitude'] = filtered_landmarks['latitude'].astype(float)
filtered_landmarks['longitude'] = filtered_landmarks['longitude'].astype(float)
filtered_landmarks = filtered_landmarks.drop(columns=['co_ordinates'])

#check missing values
print(filtered_landmarks.isnull().sum())
theme           0
sub_theme       0
feature_name    0
latitude        0
longitude       0
dtype: int64
In [96]:
filtered_landmarks.head()
Out[96]:
theme sub_theme feature_name latitude longitude
0 Education Centre Primary Schools Carlton Gardens Primary School -37.802095 144.969406
1 Education Centre Secondary Schools Melbourne Grammar School -37.834256 144.976285
2 Health Services Private Hospital Epworth Freemasons Hospital : Medical Centre -37.809344 144.982337
3 Health Services Public Hospital Royal Womens Hospital -37.798889 144.954897
4 Health Services Public Hospital Royal Childrens Hospital -37.794206 144.950048

Off-street car parks with capacity and type¶

The most recent available data for this dataset was collected in 2023, which has been used in this project due to the absence of newer data for 2024. We filtered the dataset to retain only public or mixed-use parking facilities, removing private and residential-only parking. This ensures our analysis targets parking options that are accessible to patients, students, staff and the general public, supporting real-world usability.

In [98]:
#use the data only in 2023
parking_2023 = parking_capacity[parking_capacity['census_year'] == 2023]

#define types of parking which are public spaces
public_parking_types = [
    "Commercial",
    "Commercial, Private",
    "Commercial, Residential",
    "Commercial, Private, Residential"
]

#filter the data to includes only public parkings
filtered_parking = parking_2023[parking_2023['parking_type'].isin(public_parking_types)]

#group data by lcoation
filtered_parking = filtered_parking.groupby(['latitude', 'longitude', 'building_address', 'clue_small_area'], as_index=False).agg({
    'parking_spaces': 'sum',
    'parking_type': lambda x: ', '.join(sorted(set(x)))  
})

#check missing values
print(filtered_parking.isnull().sum())
latitude            0
longitude           0
building_address    0
clue_small_area     0
parking_spaces      0
parking_type        0
dtype: int64
In [99]:
filtered_parking.head()
Out[99]:
latitude longitude building_address clue_small_area parking_spaces parking_type
0 -37.846853 144.980378 553 St Kilda Road MELBOURNE VIC 3004 Melbourne (Remainder) 183 Commercial
1 -37.846570 144.982816 Alfred Hospital 23-99 Commercial Road PRAHRAN ... Melbourne (Remainder) 1729 Commercial
2 -37.844593 144.979288 517 St Kilda Road MELBOURNE VIC 3004 Melbourne (Remainder) 8 Commercial
3 -37.837746 144.977044 415-421 St Kilda Road MELBOURNE VIC 3004 Melbourne (Remainder) 98 Commercial
4 -37.829090 144.970686 312-318 St Kilda Road SOUTHBANK VIC 3006 Southbank 240 Commercial

Pedestrian Counting System (counts per hour)¶

We filtered the pedestrian counting dataset to include only data from 2024 to the most recent available date. This allows us to understand recent trends in foot traffic near healthcare and educational facilities. By identifying areas with high pedestrian activity and their peak hours, we can align parking demand with actual usage patterns.

In [101]:
#convert sensing_date to datetime format
pedestrian_count['sensing_date'] = pd.to_datetime(pedestrian_count['sensing_date'])

#filter data to include only records from 2024
filtered_pedestrian = pedestrian_count[pedestrian_count['sensing_date'].dt.year >= 2024].copy()
#split the 'location' column into separate latitude and longitude columns
filtered_pedestrian[['latitude', 'longitude']] = filtered_pedestrian['location'].str.split(',', expand=True)

#convert string to float
filtered_pedestrian['latitude'] = filtered_pedestrian['latitude'].astype(float)
filtered_pedestrian['longitude'] = filtered_pedestrian['longitude'].astype(float)
#drop unnecessary columns
filtered_pedestrian = filtered_pedestrian.drop(columns=['id', 'direction_1', 'direction_2', 'sensor_name', 'location'])

#check missing values
print(filtered_pedestrian.isnull().sum())
location_id        0
sensing_date       0
hourday            0
pedestriancount    0
latitude           0
longitude          0
dtype: int64
In [102]:
filtered_pedestrian.head()
Out[102]:
location_id sensing_date hourday pedestriancount latitude longitude
1 47 2024-09-17 17 2167 -37.812585 144.962578
4 24 2025-04-05 8 431 -37.818880 144.954492
5 54 2024-02-24 3 18 -37.804024 144.963084
6 50 2025-03-03 4 1 -37.798082 144.967210
7 143 2025-05-08 0 71 -37.821728 144.955570

Bus stops¶

We filtered and cleaned the bus stops dataset to prepare it for analysis. The latitude and longitude information was extracted from the geo_point_2d column to make it easier to use for mapping and distance calculations. Unnecessary columns, such as asset details and shape information, were removed to keep only relevant data. This cleaned dataset allows us to easily find bus stops near healthcare and educational facilities to better understand public transport accessibility.

In [104]:
#split 'geo_point_2d' into latitude and longitude
bus_stop[['latitude', 'longitude']] = bus_stop['geo_point_2d'].str.split(',', expand=True)

bus_stop['latitude'] = bus_stop['latitude'].astype(float)
bus_stop['longitude'] = bus_stop['longitude'].astype(float)

# drop unnecessary columns
columns_to_drop = ['geo_point_2d', 'geo_shape', 'prop_id', 'addresspt1', 'addressp_1', 
                   'asset_clas', 'asset_type', 'objectid', 'str_id', 'addresspt', 
                   'asset_subt', 'model_desc', 'mcc_id']

filtered_bus_stop = bus_stop.drop(columns=columns_to_drop).copy()

#check missing values
print(bus_stop.isnull().sum())
geo_point_2d      0
geo_shape         0
prop_id           0
addresspt1        0
addressp_1        0
asset_clas        0
asset_type        0
objectid          0
str_id            0
addresspt         0
asset_subt      309
model_desc        0
mcc_id            0
roadseg_id        0
descriptio        0
model_no          0
latitude          0
longitude         0
dtype: int64
In [105]:
bus_stop.head()
Out[105]:
geo_point_2d geo_shape prop_id addresspt1 addressp_1 asset_clas asset_type objectid str_id addresspt asset_subt model_desc mcc_id roadseg_id descriptio model_no latitude longitude
0 -37.80384165792465, 144.93239283833262 {"coordinates": [144.93239283833262, -37.80384... 0 76.819824 357 Signage Sign - Public Transport 355 1235255 570648 NaN Sign - Public Transport 1 Panel 1235255 21673 Sign - Public Transport 1 Panel Bus Stop Type 13 P.16 -37.803842 144.932393
1 -37.81548699581418, 144.9581794249902 {"coordinates": [144.9581794249902, -37.815486... 0 21.561304 83 Signage Sign - Public Transport 600 1231226 548056 NaN Sign - Public Transport 1 Panel 1231226 20184 Sign - Public Transport 1 Panel Bus Stop Type 8 P.16 -37.815487 144.958179
2 -37.81353897396532, 144.95728334230756 {"coordinates": [144.95728334230756, -37.81353... 0 42.177187 207 Signage Sign - Public Transport 640 1237092 543382 NaN Sign - Public Transport 1 Panel 1237092 20186 Sign - Public Transport 1 Panel Bus Stop Type 8 P.16 -37.813539 144.957283
3 -37.82191394843844, 144.95539345270072 {"coordinates": [144.95539345270072, -37.82191... 0 15.860434 181 Signage Sign - Public Transport 918 1232777 103975 NaN Sign - Public Transport 1 Panel 1232777 22174 Sign - Public Transport 1 Panel Bus Stop Type 8 P.16 -37.821914 144.955393
4 -37.83316401267591, 144.97443745130263 {"coordinates": [144.97443745130263, -37.83316... 0 0.000000 0 Signage Sign - Public Transport 1029 1271914 0 NaN Sign - Public Transport 1 Panel 1271914 22708 Sign - Public Transport 1 Panel Bus Stop Type 8 P.16 -37.833164 144.974437

Tram tracks¶

We cleaned the tram tracks dataset to make it ready for analysis and mapping. The latitude and longitude were extracted from the geo_point_2d column so we can easily calculate distances and visualise tram locations on the map. Unnecessary columns, such as shape details and descriptions, were removed to keep the data simple and focused. This cleaned dataset allows us to find tram tracks near healthcare and educational facilities to help assess how well public transport serves these areas.

In [107]:
#split 'geo_point_2d' into latitude and longitude
tram_stop[['latitude', 'longitude']] = tram_stop['geo_point_2d'].str.split(',', expand=True)

tram_stop['latitude'] = tram_stop['latitude'].astype(float)
tram_stop['longitude'] = tram_stop['longitude'].astype(float)

# drop unnecessary columns
columns_to_drop = ['geo_point_2d', 'geo_shape', 'descriptio']

filtered_tram_stop = tram_stop.drop(columns=columns_to_drop).copy()

# check missing values
print(tram_stop.isnull().sum())
geo_point_2d    0
geo_shape       0
descriptio      0
name            0
latitude        0
longitude       0
dtype: int64
In [108]:
tram_stop.head()
Out[108]:
geo_point_2d geo_shape descriptio name latitude longitude
0 -37.78861331868605, 144.93461561782556 {"coordinates": [[[[144.934525400489, -37.7886... <center><table><tr><th colspan='2' align='cent... kml_3 -37.788613 144.934616
1 -37.819185576253524, 144.9610352542915 {"coordinates": [[[[144.96099441333, -37.81917... <center><table><tr><th colspan='2' align='cent... kml_5 -37.819186 144.961035
2 -37.81837971233182, 144.95945259116502 {"coordinates": [[[[144.959343869512, -37.8182... <center><table><tr><th colspan='2' align='cent... kml_6 -37.818380 144.959453
3 -37.81440385390417, 144.9702510191494 {"coordinates": [[[[144.969150077924, -37.8147... <center><table><tr><th colspan='2' align='cent... kml_7 -37.814404 144.970251
4 -37.816738613153454, 144.9699088149105 {"coordinates": [[[[144.970082905454, -37.8167... <center><table><tr><th colspan='2' align='cent... kml_8 -37.816739 144.969909

Data Integration¶

Linking landmarks with nearby public parking¶

We combined data from landmarks (such as hospitals and schools) with nearby public parking locations. The distance between each landmark and every car park was calculated using their geographic coordinates. If a car park was located within 400 metres of a landmark, it was counted as nearby, and its number of parking spaces was included in the total. We also recorded the building addresses of these car parks to provide more location context.

In [111]:
landmark_parking = []

for _, landmark in filtered_landmarks.iterrows():
    landmark_location = (landmark['latitude'], landmark['longitude'])

    #find nearby car parks within 400 metres
    nearby_parks = filtered_parking[filtered_parking.apply(
        lambda x: geodesic(landmark_location, (x['latitude'], x['longitude'])).meters <= 400,
        axis=1
    )]

    total_spaces = nearby_parks['parking_spaces'].sum()
    carpark_count = len(nearby_parks)

    #address of nearby parking
    building_addresses = nearby_parks['building_address'].unique().tolist()

    landmark_parking.append({
        'feature_name': landmark['feature_name'],
        'theme': landmark['theme'],
        'latitude': landmark['latitude'],
        'longitude': landmark['longitude'],
        'nearby_public_carparks': carpark_count,
        'total_public_spaces': total_spaces,
        'parking_addresses': building_addresses
    })
#change to dataframe
landmark_parking_df = pd.DataFrame(landmark_parking)
In [112]:
landmark_parking_df.head()
#print(landmark_parking_df.iloc[2]['parking_addresses'])
Out[112]:
feature_name theme latitude longitude nearby_public_carparks total_public_spaces parking_addresses
0 Carlton Gardens Primary School Education Centre -37.802095 144.969406 3 1500 [Melbourne Museum 11 Nicholson Street CARLTON ...
1 Melbourne Grammar School Education Centre -37.834256 144.976285 1 98 [415-421 St Kilda Road MELBOURNE VIC 3004]
2 Epworth Freemasons Hospital : Medical Centre Health Services -37.809344 144.982337 3 860 [94-106 Grey Street EAST MELBOURNE VIC 3002, T...
3 Royal Womens Hospital Health Services -37.798889 144.954897 5 2713 [14-20 Blackwood Street NORTH MELBOURNE VIC 30...
4 Royal Childrens Hospital Health Services -37.794206 144.950048 1 2000 [Royal Childrens Hospital 48-50 Flemington Roa...

Linking landmarks with pedestrian data¶

Each landmark was matched with pedestrian sensors located within a 400-metre radius. For each location, we calculated the average number of pedestrians detected nearby using 2024 sensor data. This provides an indication of foot traffic volume around that site. If no sensor was found near a landmark, the average pedestrian count was set to zero.

In [115]:
landmark_pedestrian = []

for _, landmark in filtered_landmarks.iterrows():
    landmark_location = (landmark['latitude'], landmark['longitude'])

    # find nearby sensors
    nearby_sensors = filtered_pedestrian[filtered_pedestrian.apply(
        lambda x: geodesic(landmark_location, (x['latitude'], x['longitude'])).meters <= 400, axis=1)]

    # calculate average pedestrian count
    avg_ped = nearby_sensors['pedestriancount'].mean() if not nearby_sensors.empty else 0

    # calculate peak hour and pedestrian count during that hour
    if not nearby_sensors.empty:
        hourly_profile = nearby_sensors.groupby('hourday')['pedestriancount'].mean()
        peak_hour = int(hourly_profile.idxmax())
        peak_pedestrian_count = int(hourly_profile.max())
    else:
        peak_hour = 'no data'
        peak_pedestrian_count = 0

    landmark_pedestrian.append({
        'feature_name': landmark['feature_name'],
        'avg_pedestrian_count': round(avg_ped, 2),
        'peak_hour': peak_hour,
        'peak_pedestrian_count': peak_pedestrian_count
    })

landmark_pedestrian_df = pd.DataFrame(landmark_pedestrian)
In [116]:
#If no sensor was found near a landmark, the average pedestrian count was set to zero
landmark_pedestrian_df['pedestrian_display'] = landmark_pedestrian_df['avg_pedestrian_count'].apply(
    lambda x: 'no data' if x == 0 else x
)
In [117]:
landmark_pedestrian_df.head()
Out[117]:
feature_name avg_pedestrian_count peak_hour peak_pedestrian_count pedestrian_display
0 Carlton Gardens Primary School 181.88 20 436 181.88
1 Melbourne Grammar School 0.00 no data 0 no data
2 Epworth Freemasons Hospital : Medical Centre 0.00 no data 0 no data
3 Royal Womens Hospital 0.00 no data 0 no data
4 Royal Childrens Hospital 0.00 no data 0 no data

Combining parking and pedestrian data for each landmark¶

The parking and pedestrian datasets were merged using the landmark’s feature_name as a common key. This created a single summary table that includes both the total nearby parking spaces and the average pedestrian activity for each landmark. This integrated view helps assess demand and accessibility at each location.

In [119]:
landmark_access_summary = landmark_parking_df.merge(landmark_pedestrian_df, on='feature_name', how='left')
landmark_access_summary.head()
In [120]:
landmark_access_summary.head(10)
Out[120]:
feature_name theme latitude longitude nearby_public_carparks total_public_spaces parking_addresses avg_pedestrian_count peak_hour peak_pedestrian_count pedestrian_display
0 Carlton Gardens Primary School Education Centre -37.802095 144.969406 3 1500 [Melbourne Museum 11 Nicholson Street CARLTON ... 181.88 20 436 181.88
1 Melbourne Grammar School Education Centre -37.834256 144.976285 1 98 [415-421 St Kilda Road MELBOURNE VIC 3004] 0.00 no data 0 no data
2 Epworth Freemasons Hospital : Medical Centre Health Services -37.809344 144.982337 3 860 [94-106 Grey Street EAST MELBOURNE VIC 3002, T... 0.00 no data 0 no data
3 Royal Womens Hospital Health Services -37.798889 144.954897 5 2713 [14-20 Blackwood Street NORTH MELBOURNE VIC 30... 0.00 no data 0 no data
4 Royal Childrens Hospital Health Services -37.794206 144.950048 1 2000 [Royal Childrens Hospital 48-50 Flemington Roa... 0.00 no data 0 no data
5 North Melbourne Primary School Education Centre -37.798674 144.951066 1 958 [Royal Womens Hospital 18-22 Flemington Road P... 0.00 no data 0 no data
6 BIO 21 Institute Education Centre -37.797874 144.953780 3 2182 [Victorian Comprehensive Cancer Centre 301-327... 0.00 no data 0 no data
7 Royal Melbourne Hospital Health Services -37.799307 144.956390 6 3849 [14-20 Blackwood Street NORTH MELBOURNE VIC 30... 0.00 no data 0 no data
8 RMIT University Education Centre -37.808080 144.964453 6 3197 [Melbourne Central 183-265 La Trobe Street MEL... 701.35 17 1434 701.35
9 Peter Maccallum Cancer Institute Health Services -37.811477 144.977401 2 624 [33-61 Cathedral Place EAST MELBOURNE VIC 3002... 0.00 no data 0 no data

Check for shared or overlapping car parks¶

We analysed whether certain public car parks were counted for multiple landmarks. This helps identify car parks that may serve more than one nearby facility, which is important when considering shared resources or potential congestion.

In [122]:
all_parks = landmark_access_summary.explode('parking_addresses') 

#count how many times each address is linked to a landmark
shared_counts = all_parks['parking_addresses'].value_counts()
shared_parks = shared_counts[shared_counts > 1]

print("Shared car parks (used by more than one landmark):")
print(shared_parks)
Shared car parks (used by more than one landmark):
parking_addresses
Royal Womens Hospital 18-22 Flemington Road PARKVILLE VIC 3050                     6
Victorian Comprehensive Cancer Centre 301-327 Grattan Street MELBOURNE VIC 3000    5
Royal Melbourne Hospital 300-328 Grattan Street PARKVILLE VIC 3050                 5
Carpark Melbourne University 220 Berkeley Street CARLTON VIC 3053                  4
94-106 Grey Street EAST MELBOURNE VIC 3002                                         3
The Albert 158 Albert Street EAST MELBOURNE VIC 3002                               3
298-336 Victoria Parade EAST MELBOURNE VIC 3002                                    3
RMIT Village 5-17 Flemington Road NORTH MELBOURNE VIC 3051                         3
Kenneth Myer Building 144 30 Royal Parade PARKVILLE VIC 3010                       2
33-61 Cathedral Place EAST MELBOURNE VIC 3002                                      2
553 St Kilda Road MELBOURNE VIC 3004                                               2
6-30 Tyne Street CARLTON VIC 3053                                                  2
368-386 Lygon Street CARLTON VIC 3053                                              2
265-281 Faraday Street CARLTON VIC 3053                                            2
204-218 Lygon Street CARLTON VIC 3053                                              2
410-418 Albert Street EAST MELBOURNE VIC 3002                                      2
58-66 La Trobe Street MELBOURNE VIC 3000                                           2
QV Retail & Parking 221 Little Lonsdale Street MELBOURNE VIC 3000                  2
Melbourne Central 183-265 La Trobe Street MELBOURNE VIC 3000                       2
14-20 Blackwood Street NORTH MELBOURNE VIC 3051                                    2
Carlton Clocktower Complex 247-253 Drummond Street CARLTON VIC 3053                2
Alfred Hospital 23-99 Commercial Road PRAHRAN VIC 3181                             2
Name: count, dtype: int64

We found that some car parks are used by several landmarks:

  • The car park at Royal Women’s Hospital (18–22 Flemington Road, Parkville) is the most shared one, connected to 6 different places nearby.
  • Other car parks, like those on Grattan Street and Berkeley Street, are also used by 4 or 5 landmarks.
  • Many shared car parks are found in areas like Carlton, East Melbourne, and North Melbourne.

Combining public transport data for each landmark¶

The nearby bus stops and tram tracks were calculated based on their distance to each landmark. Landmarks within 400 metres of bus stops or tram tracks were counted and stored as nearby_bus_stops and nearby_tram_tracks. These results were then merged into the landmark summary table.

This helps provide a more complete view of each location’s public transport accessibility, which may influence parking demand and ease of access.

In [125]:
nearby_bus_counts = []

for _, landmark in filtered_landmarks.iterrows():
    landmark_location = (landmark['latitude'], landmark['longitude'])
    
    #find nearby bus stops within 400m
    nearby_buses = filtered_bus_stop[filtered_bus_stop.apply(
        lambda x: geodesic(landmark_location, (x['latitude'], x['longitude'])).meters <= 400,
        axis=1
    )]

    nearby_bus_counts.append({
        'feature_name': landmark['feature_name'],
        'nearby_bus_stops': len(nearby_buses)
    })

# Convert to DataFrame
nearby_bus_df = pd.DataFrame(nearby_bus_counts)

# Create a list to store results
nearby_tram_counts = []

for _, landmark in filtered_landmarks.iterrows():
    landmark_location = (landmark['latitude'], landmark['longitude'])
    
    # Find nearby tram tracks within 400m
    nearby_trams = filtered_tram_stop[filtered_tram_stop.apply(
        lambda x: geodesic(landmark_location, (x['latitude'], x['longitude'])).meters <= 400,
        axis=1
    )]

    nearby_tram_counts.append({
        'feature_name': landmark['feature_name'],
        'nearby_tram_tracks': len(nearby_trams)
    })

# Convert to DataFrame
nearby_tram_df = pd.DataFrame(nearby_tram_counts)

# Merge bus stops
landmark_access_summary = landmark_access_summary.merge(nearby_bus_df, on='feature_name', how='left')

# Merge tram tracks
landmark_access_summary = landmark_access_summary.merge(nearby_tram_df, on='feature_name', how='left')

# Check the result
landmark_access_summary.head()
Out[125]:
feature_name theme latitude longitude nearby_public_carparks total_public_spaces parking_addresses avg_pedestrian_count peak_hour peak_pedestrian_count pedestrian_display nearby_bus_stops nearby_tram_tracks
0 Carlton Gardens Primary School Education Centre -37.802095 144.969406 3 1500 [Melbourne Museum 11 Nicholson Street CARLTON ... 181.88 20 436 181.88 14 0
1 Melbourne Grammar School Education Centre -37.834256 144.976285 1 98 [415-421 St Kilda Road MELBOURNE VIC 3004] 0.00 no data 0 no data 6 13
2 Epworth Freemasons Hospital : Medical Centre Health Services -37.809344 144.982337 3 860 [94-106 Grey Street EAST MELBOURNE VIC 3002, T... 0.00 no data 0 no data 0 12
3 Royal Womens Hospital Health Services -37.798889 144.954897 5 2713 [14-20 Blackwood Street NORTH MELBOURNE VIC 30... 0.00 no data 0 no data 13 29
4 Royal Childrens Hospital Health Services -37.794206 144.950048 1 2000 [Royal Childrens Hospital 48-50 Flemington Roa... 0.00 no data 0 no data 7 11

Shared parking space adjustment¶

We adjust the number of public parking spaces available to each hospital or school when those spaces are shared with other nearby locations. It starts by listing every combination of landmarks and the car parks near them. Then, it counts how many landmarks are linked to each parking area. Instead of giving each landmark the full parking count, it divides the total equally among the ones sharing it. Finally, it adds up the adjusted numbers for each landmark, giving a more accurate view of how much parking is truly available to them. This helps avoid double-counting and supports better planning for areas where parking is shared.

In [127]:
shared_counts_dict = shared_counts.to_dict()

#shared_by = how many times each parking_address 
all_parks['shared_by'] = all_parks['parking_addresses'].map(shared_counts_dict)

#divide a landmark’s total spaces equally across its car parks
all_parks['shared_space'] = all_parks['total_public_spaces'] / all_parks.groupby('feature_name')['parking_addresses'].transform('count')

#adjust shared space based on how many landmarks use each car park
all_parks['adjusted_space'] = all_parks['shared_space'] / all_parks['shared_by']

#sum up adjusted spaces per landmark
adjusted_parking_summary = all_parks.groupby('feature_name', as_index=False)['adjusted_space'].sum()
adjusted_parking_summary.rename(columns={'adjusted_space': 'adjusted_total_public_spaces'}, inplace=True)

#merge back with original summary
landmark_access_summary = landmark_access_summary.merge(adjusted_parking_summary, on='feature_name', how='left')
landmark_access_summary.head()
Out[127]:
feature_name theme latitude longitude nearby_public_carparks total_public_spaces parking_addresses avg_pedestrian_count peak_hour peak_pedestrian_count pedestrian_display nearby_bus_stops nearby_tram_tracks adjusted_total_public_spaces
0 Carlton Gardens Primary School Education Centre -37.802095 144.969406 3 1500 [Melbourne Museum 11 Nicholson Street CARLTON ... 181.88 20 436 181.88 14 0 1000.000000
1 Melbourne Grammar School Education Centre -37.834256 144.976285 1 98 [415-421 St Kilda Road MELBOURNE VIC 3004] 0.00 no data 0 no data 6 13 98.000000
2 Epworth Freemasons Hospital : Medical Centre Health Services -37.809344 144.982337 3 860 [94-106 Grey Street EAST MELBOURNE VIC 3002, T... 0.00 no data 0 no data 0 12 286.666667
3 Royal Womens Hospital Health Services -37.798889 144.954897 5 2713 [14-20 Blackwood Street NORTH MELBOURNE VIC 30... 0.00 no data 0 no data 13 29 759.640000
4 Royal Childrens Hospital Health Services -37.794206 144.950048 1 2000 [Royal Childrens Hospital 48-50 Flemington Roa... 0.00 no data 0 no data 7 11 2000.000000
In [128]:
all_parks.head(10)
Out[128]:
feature_name theme latitude longitude nearby_public_carparks total_public_spaces parking_addresses avg_pedestrian_count peak_hour peak_pedestrian_count pedestrian_display shared_by shared_space adjusted_space
0 Carlton Gardens Primary School Education Centre -37.802095 144.969406 3 1500 Melbourne Museum 11 Nicholson Street CARLTON V... 181.88 20 436 181.88 1.0 500.000000 500.000000
0 Carlton Gardens Primary School Education Centre -37.802095 144.969406 3 1500 204-218 Lygon Street CARLTON VIC 3053 181.88 20 436 181.88 2.0 500.000000 250.000000
0 Carlton Gardens Primary School Education Centre -37.802095 144.969406 3 1500 Carlton Clocktower Complex 247-253 Drummond St... 181.88 20 436 181.88 2.0 500.000000 250.000000
1 Melbourne Grammar School Education Centre -37.834256 144.976285 1 98 415-421 St Kilda Road MELBOURNE VIC 3004 0.00 no data 0 no data 1.0 98.000000 98.000000
2 Epworth Freemasons Hospital : Medical Centre Health Services -37.809344 144.982337 3 860 94-106 Grey Street EAST MELBOURNE VIC 3002 0.00 no data 0 no data 3.0 286.666667 95.555556
2 Epworth Freemasons Hospital : Medical Centre Health Services -37.809344 144.982337 3 860 The Albert 158 Albert Street EAST MELBOURNE VI... 0.00 no data 0 no data 3.0 286.666667 95.555556
2 Epworth Freemasons Hospital : Medical Centre Health Services -37.809344 144.982337 3 860 298-336 Victoria Parade EAST MELBOURNE VIC 3002 0.00 no data 0 no data 3.0 286.666667 95.555556
3 Royal Womens Hospital Health Services -37.798889 144.954897 5 2713 14-20 Blackwood Street NORTH MELBOURNE VIC 3051 0.00 no data 0 no data 2.0 542.600000 271.300000
3 Royal Womens Hospital Health Services -37.798889 144.954897 5 2713 RMIT Village 5-17 Flemington Road NORTH MELBOU... 0.00 no data 0 no data 3.0 542.600000 180.866667
3 Royal Womens Hospital Health Services -37.798889 144.954897 5 2713 Victorian Comprehensive Cancer Centre 301-327 ... 0.00 no data 0 no data 5.0 542.600000 108.520000

Parking Demand Analysis¶

Parking Demand Risk Classification¶

We calculated the parking demand risk by comparing the number of available parking spaces to the number of pedestrians during peak hours. A new value, parking_per_peak_person, was calculated to represent how many parking spaces are available for each person during the busiest time. Based on this ratio, we classified each landmark into demand risk levels: High, Moderate, or Low. This helps identify locations where parking may be insufficient during peak times and supports decision-making for parking improvements.

In [130]:
# calculate parking per person during peak hour
landmark_access_summary['parking_per_peak_person'] = (
    landmark_access_summary['adjusted_total_public_spaces'] / (landmark_access_summary['peak_pedestrian_count'] + 1)
)

#demand risk level
def classify_risk(ratio):
    if ratio['peak_pedestrian_count'] == 0:
        return 'Unknown'
    elif ratio['parking_per_peak_person'] < 0.5:
        return 'High'
    elif ratio['parking_per_peak_person'] < 1.5:
        return 'Moderate'
    else:
        return 'Low'

landmark_access_summary['demand_risk'] = landmark_access_summary.apply(classify_risk, axis=1)
In [131]:
landmark_access_summary
Out[131]:
feature_name theme latitude longitude nearby_public_carparks total_public_spaces parking_addresses avg_pedestrian_count peak_hour peak_pedestrian_count pedestrian_display nearby_bus_stops nearby_tram_tracks adjusted_total_public_spaces parking_per_peak_person demand_risk
0 Carlton Gardens Primary School Education Centre -37.802095 144.969406 3 1500 [Melbourne Museum 11 Nicholson Street CARLTON ... 181.88 20 436 181.88 14 0 1000.000000 2.288330 Low
1 Melbourne Grammar School Education Centre -37.834256 144.976285 1 98 [415-421 St Kilda Road MELBOURNE VIC 3004] 0.00 no data 0 no data 6 13 98.000000 98.000000 Unknown
2 Epworth Freemasons Hospital : Medical Centre Health Services -37.809344 144.982337 3 860 [94-106 Grey Street EAST MELBOURNE VIC 3002, T... 0.00 no data 0 no data 0 12 286.666667 286.666667 Unknown
3 Royal Womens Hospital Health Services -37.798889 144.954897 5 2713 [14-20 Blackwood Street NORTH MELBOURNE VIC 30... 0.00 no data 0 no data 13 29 759.640000 759.640000 Unknown
4 Royal Childrens Hospital Health Services -37.794206 144.950048 1 2000 [Royal Childrens Hospital 48-50 Flemington Roa... 0.00 no data 0 no data 7 11 2000.000000 2000.000000 Unknown
5 North Melbourne Primary School Education Centre -37.798674 144.951066 1 958 [Royal Womens Hospital 18-22 Flemington Road P... 0.00 no data 0 no data 10 11 159.666667 159.666667 Unknown
6 BIO 21 Institute Education Centre -37.797874 144.953780 3 2182 [Victorian Comprehensive Cancer Centre 301-327... 0.00 no data 0 no data 13 17 412.155556 412.155556 Unknown
7 Royal Melbourne Hospital Health Services -37.799307 144.956390 6 3849 [14-20 Blackwood Street NORTH MELBOURNE VIC 30... 0.00 no data 0 no data 12 29 1058.475000 1058.475000 Unknown
8 RMIT University Education Centre -37.808080 144.964453 6 3197 [Melbourne Central 183-265 La Trobe Street MEL... 701.35 17 1434 701.35 3 34 2397.750000 1.670906 Low
9 Peter Maccallum Cancer Institute Health Services -37.811477 144.977401 2 624 [33-61 Cathedral Place EAST MELBOURNE VIC 3002... 0.00 no data 0 no data 0 19 312.000000 312.000000 Unknown
10 Epworth Freemasons Hospital Health Services -37.810971 144.983700 3 860 [94-106 Grey Street EAST MELBOURNE VIC 3002, T... 0.00 no data 0 no data 0 11 286.666667 286.666667 Unknown
11 Melbourne Private Hospital Health Services -37.798311 144.957288 6 3658 [RMIT Village 5-17 Flemington Road NORTH MELBO... 0.00 no data 0 no data 14 27 1005.950000 1005.950000 Unknown
12 The Royal Victorian Eye & Ear Hospital Health Services -37.808752 144.976268 3 1124 [33-61 Cathedral Place EAST MELBOURNE VIC 3002... 232.86 12 643 232.86 0 27 749.333333 1.163561 Moderate
13 Kangan Batman Tafe Education Centre -37.822182 144.948908 4 1396 [Melbourne Convention Centre Carpark 671-701 F... 386.11 8 1159 386.11 0 19 1396.000000 1.203448 Moderate
14 University of Melbourne Education Centre -37.798289 144.960995 3 1736 [Carpark Melbourne University 220 Berkeley Str... 150.05 17 336 150.05 11 11 723.333333 2.146390 Low
15 Carlton Primary School Education Centre -37.795908 144.970147 2 369 [368-386 Lygon Street CARLTON VIC 3053, 6-30 T... 223.98 13 448 223.98 17 10 184.500000 0.410913 High
16 Wesley College Education Centre -37.848520 144.982228 2 1912 [553 St Kilda Road MELBOURNE VIC 3004, Alfred ... 0.00 no data 0 no data 7 8 956.000000 956.000000 Unknown
17 University of Melbourne (VCA and Music) Education Centre -37.824114 144.969333 2 1286 [NGV International 130-200 St Kilda Road SOUTH... 743.19 13 1683 743.19 12 12 1286.000000 0.763658 Moderate
18 University High School Education Centre -37.797314 144.956034 3 2182 [Victorian Comprehensive Cancer Centre 301-327... 0.00 no data 0 no data 11 21 412.155556 412.155556 Unknown
19 Kensington Primary School Education Centre -37.792524 144.927109 0 0 [] 80.48 12 168 80.48 5 0 0.000000 0.000000 High
20 Alfred Hospital Health Services -37.846263 144.981786 3 1920 [553 St Kilda Road MELBOURNE VIC 3004, Alfred ... 0.00 no data 0 no data 6 6 1280.000000 1280.000000 Unknown
21 Royal Dental Hospital Health Services -37.799507 144.964527 8 3939 [204-218 Lygon Street CARLTON VIC 3053, Carpar... 175.96 17 338 175.96 15 14 2338.781250 6.899060 Low
22 Melbourne Girls Grammar School Education Centre -37.831536 144.985089 0 0 [] 0.00 no data 0 no data 2 1 0.000000 0.000000 Unknown
23 Mercy Private Hospital Health Services -37.811897 144.984436 3 860 [94-106 Grey Street EAST MELBOURNE VIC 3002, T... 0.00 no data 0 no data 0 8 286.666667 286.666667 Unknown
24 State Library Victoria Community Use -37.809985 144.964330 10 3946 [222-260 Elizabeth Street MELBOURNE VIC 3000, ... 627.02 17 1301 627.02 2 36 3354.100000 2.576114 Low
In [132]:
landmark_access_summary[landmark_access_summary['demand_risk'] == 'High'].head()
Out[132]:
feature_name theme latitude longitude nearby_public_carparks total_public_spaces parking_addresses avg_pedestrian_count peak_hour peak_pedestrian_count pedestrian_display nearby_bus_stops nearby_tram_tracks adjusted_total_public_spaces parking_per_peak_person demand_risk
15 Carlton Primary School Education Centre -37.795908 144.970147 2 369 [368-386 Lygon Street CARLTON VIC 3053, 6-30 T... 223.98 13 448 223.98 17 10 184.5 0.410913 High
19 Kensington Primary School Education Centre -37.792524 144.927109 0 0 [] 80.48 12 168 80.48 5 0 0.0 0.000000 High

Pedestrian Demand vs Parking Capacity Analysis¶

To create these visualisations, we wrote code that automatically generated hourly charts for all landmarks with available pedestrian sensor data. First, we filtered the landmarks to only include those with sensor data (peak_pedestrian_count > 0). Then, for each landmark, we identified nearby pedestrian sensors within 400 metres and calculated the average pedestrian count for each hour of the day. Next, we retrieved the adjusted parking spaces for each landmark and plotted this as a red dashed line across the chart to indicate parking capacity. The hourly pedestrian counts were plotted as blue lines with dots, showing how foot traffic changes throughout the day. Finally, we used subplots to display multiple landmarks side by side, making it easy to compare pedestrian demand and parking supply across different locations. This automated approach allows us to efficiently visualise and assess parking adequacy for all monitored landmarks.

By plotting pedestrian activity throughout the day against the available parking spaces, we can observe how demand fluctuates and whether parking supply is sufficient during busy times.

In [134]:
#filter landmarks with sensor data
landmarks_with_sensor = landmark_access_summary[landmark_access_summary['peak_pedestrian_count'] > 0]

total_landmarks = len(landmarks_with_sensor)
plots_per_row = 2
total_rows = math.ceil(total_landmarks / plots_per_row)


fig, axs = plt.subplots(total_rows, plots_per_row, figsize=(15, total_rows * 5))
axs = axs.flatten()  # make it easier to index

for i, (_, row) in enumerate(landmarks_with_sensor.iterrows()):
    landmark_name = row['feature_name']

    #landmark location
    landmark_row = filtered_landmarks[filtered_landmarks['feature_name'] == landmark_name].iloc[0]
    landmark_location = (landmark_row['latitude'], landmark_row['longitude'])

    #find nearby pedestrian sensors
    nearby_sensors = filtered_pedestrian[filtered_pedestrian.apply(
        lambda x: geodesic(landmark_location, (x['latitude'], x['longitude'])).meters <= 400, axis=1
    )]

    ped_by_hour = nearby_sensors.groupby('hourday')['pedestriancount'].mean().reset_index()
    adjusted_spaces = row['adjusted_total_public_spaces']

    ax = axs[i]
    ax.plot(ped_by_hour['hourday'], ped_by_hour['pedestriancount'], marker='o', label='Pedestrian Count')
    ax.axhline(y=adjusted_spaces, color='red', linestyle='--', label='Adjusted Parking Spaces')
    ax.set_title(f"{landmark_name}", fontsize=12)
    ax.set_xlabel("Hour of Day")
    ax.set_ylabel("Pedestrian Count")
    ax.set_xticks(range(0, 24))
    ax.legend()
    ax.grid(True)

if total_landmarks % plots_per_row != 0:
    for j in range(total_landmarks, len(axs)):
        fig.delaxes(axs[j])

plt.tight_layout()
plt.show()
No description has been provided for this image

Key Insights:¶

  • Some landmarks, like Carlton Gardens Primary School and Carlton Primary School, showed peak pedestrian counts that approached or exceeded their parking availability during the day. This indicates potential high demand or risk of parking shortages at certain peak times.

  • Major universities such as RMIT University and University of Melbourne (VCA and Music) experienced very high pedestrian flows, yet still maintained parking supply above the peak pedestrian counts, suggesting relatively good parking accessibility.

  • Hospitals (e.g., Royal Victorian Eye & Ear Hospital and Royal Dental Hospital) also had times where pedestrian volumes increased notably, but most remained within the parking capacity limits.

  • State Library Victoria, although not a healthcare or education institution, showed steady foot traffic throughout the day but still within the parking capacity.

In summary, most landmarks generally have enough parking capacity during peak pedestrian times. However, smaller facilities and some schools may face challenges during peak hours. These areas may require further monitoring or parking management solutions.

Data visualisation¶

Interactive Map of landmark accessibility¶

This interactive map visualises key public landmarks in Melbourne—such as hospitals, schools, and community facilities—alongside parking and pedestrian activity data.

What the Map Shows:¶

  1. City Centered View
    The map is centered on Melbourne’s CBD using its geographic coordinates (latitude: -37.8136, longitude: 144.9631) for a clear view of the city.

  2. Landmark Markers
    Each landmark is marked with a colored icon:

    • Blue marker: There is pedestrian sensor data available for this location.
    • Gray marker: No pedestrian data is available.

    Clicking on any marker shows key details:

    • Name of the location
    • Type (e.g., hospital, school)
    • Number of nearby public car parks
    • Estimated parking space availability
    • Average pedestrian count (if available)
    • Peak pedestrian hour (if available)
  3. Sensor Locations
    Green circles show the locations of pedestrian sensors, helping to visualise where foot traffic is being monitored.

In [137]:
map_center = [-37.8136, 144.9631]
melb_map = folium.Map(location=map_center, zoom_start=14)

unique_sensors = filtered_pedestrian[['location_id', 'latitude', 'longitude']].drop_duplicates()
unique_sensors.head()

for _, row in landmark_access_summary.iterrows():
    #check if sensor data exists
    has_sensor = row['avg_pedestrian_count'] != 0

    marker_color = 'blue' if has_sensor else 'gray'

    avg_ped_display = 'no data' if not has_sensor else row['avg_pedestrian_count']
    adjusted_space = round(row['adjusted_total_public_spaces'], 1) if not pd.isna(row['adjusted_total_public_spaces']) else 'no data'
    peak_hour_display = f"<b>Peak Hour:</b> {row['peak_hour']}:00" if has_sensor and row['peak_hour'] != 'no data' else ""

    popup_text = f"""
    <b>{row['feature_name']}</b><br>
    Type: {row['theme']}<br>
    Nearby Public Car Parks: {row['nearby_public_carparks']}<br>
    Adjusted Parking Spaces: {adjusted_space}<br>
    Avg Pedestrian Count: {avg_ped_display}<br>
    {peak_hour_display}
    """

    folium.Marker(
        location=[row['latitude'], row['longitude']],
        popup=folium.Popup(popup_text, max_width=500),
        icon=folium.Icon(color=marker_color, icon='info-sign')
    ).add_to(melb_map)

    for _, row in unique_sensors.iterrows():
        folium.CircleMarker(
            location=[row['latitude'], row['longitude']],
            radius=4,
            color='green',
            fill=True,
            fill_opacity=0.7,
            popup=f"Sensor Location ID: {row['location_id']}"
        ).add_to(melb_map)

legend = MacroElement()
#legend._template = Template(legend_html)
melb_map.get_root().add_child(legend)

melb_map
Out[137]:
Make this Notebook Trusted to load map: File -> Trust Notebook

This map shows important public landmarks in Melbourne,like hospitals, schools, and libraries along with useful information about parking and pedestrian activity nearby. Each blue marker represents one of these landmarks. When you click on a marker, a box pops up with these details, which are type, nearby public car parks, total parking spaces, avg pedestrian count, and peak hour.

Parking Demand and Public Transport Access in Melbourne¶

This interactive map gives a clearer picture of how busy certain areas are and how well they are supported by public transport. It focuses on landmarks with valid pedestrian sensor data, and it also shows the locations of bus and tram stops.


What This Map Shows¶

  1. Landmarks with Parking Demand Risk

    • Each landmark is marked with a colored icon based on parking demand risk:
      • Red = High demand (likely parking shortage)
      • Orange = Moderate demand
      • Green = Low demand
      • Gray = Unknown or missing data
    • Clicking a marker shows:
      • Name and type of the place (e.g., hospital, university)
      • Peak pedestrian count (how many people walk past at the busiest time)
      • Estimated available parking spaces
      • Risk level for parking demand
  2. Bus and Tram Stops

    • Orange dots show bus stops near landmarks.
    • Purple dots show tram stops nearby.
    • These help assess if public transport is available as an alternative to car parking.
  3. Custom Legend

    • A built-in legend explains all the marker colors clearly, helping users interpret the map without needing extra guidance.

Key insight¶

  • Helps identify which places are struggling with parking due to high pedestrian activity.
  • Shows whether public transport options are nearby, offering possible solutions to reduce car usage.
  • Supports planning decisions for infrastructure improvements, policy changes, or encouraging alternative transport modes.

This map turns complex urban data into an easy-to-understand, visual story that can help shape smarter transport and planning strategies in Melbourne.

In [140]:
map_center = [-37.8136, 144.9631]
combined_map = folium.Map(location=map_center, zoom_start=14)

#colors for demand risk
risk_colors = {
    'Low': 'green',
    'Moderate': 'orange',
    'High': 'red'
}

#add landmarks with demand risk colors (Only with valid sensor data)
valid_data = landmark_access_summary[landmark_access_summary['peak_pedestrian_count'] > 0]

for _, row in valid_data.iterrows():
    risk_level = row['demand_risk']
    marker_color = risk_colors.get(risk_level, 'gray')

    popup_text = f"""
    <b>{row['feature_name']}</b><br>
    Type: {row['theme']}<br>
    Peak Pedestrian Count: {row['peak_pedestrian_count']}<br>
    Adjusted Parking Spaces: {round(row['adjusted_total_public_spaces'], 1)}<br>
    Parking Demand Risk: <b>{risk_level}</b>
    """

    folium.Marker(
        location=[row['latitude'], row['longitude']],
        popup=folium.Popup(popup_text, max_width=400),
        icon=folium.Icon(color=marker_color, icon='info-sign')
    ).add_to(combined_map)

#add Bus Stops
for _, row in filtered_bus_stop.iterrows():
    folium.CircleMarker(
        location=[row['latitude'], row['longitude']],
        radius=3,
        color='orange',
        fill=True,
        fill_opacity=0.7,
        popup="Bus Stop"
    ).add_to(combined_map)

#add Tram Stops
for _, row in filtered_tram_stop.iterrows():
    folium.CircleMarker(
        location=[row['latitude'], row['longitude']],
        radius=3,
        color='purple',
        fill=True,
        fill_opacity=0.7,
        popup="Tram Stop"
    ).add_to(combined_map)

legend_html = """
<div style="
    position: fixed; 
    bottom: 50px; left: 50px; width: 230px; height: 160px; 
    background-color: white; border:2px solid grey; z-index:9999; font-size:14px;
    padding: 10px;">
    <b>Map Legend</b><br>
    <div style='display:inline-block;width:12px;height:12px;background-color:red;margin-right:10px;'></div> High Parking Demand Risk<br>
    <div style='display:inline-block;width:12px;height:12px;background-color:orange;margin-right:10px;'></div> Moderate Parking Demand Risk<br>
    <div style='display:inline-block;width:12px;height:12px;background-color:green;margin-right:10px;'></div> Low Parking Demand Risk<br>
    <div style='display:inline-block;width:12px;height:12px;background-color:gray;margin-right:10px;'></div> Unknown Parking Demand Risk<br>
    <div style='display:inline-block;width:12px;height:12px;background-color:orange;margin-right:10px;'></div> Bus Stops<br>
    <div style='display:inline-block;width:12px;height:12px;background-color:purple;margin-right:10px;'></div> Tram Stops
</div>
"""

legend = MacroElement()
legend._template = Template(legend_html)
combined_map.get_root().add_child(legend)

combined_map
Out[140]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Parking demand summary¶

This code groups landmarks based on their parking demand risk level (High, Moderate, or Low) and list the names of landmarks under each category. It helps present the data in a simple, structured format that makes it easy for stakeholders to quickly understand which locations face the highest pressure for parking and where improvements might be needed.

In [142]:
risk_groups = landmark_access_summary.groupby('demand_risk')['feature_name'].apply(list)

markdown_text = "### Parking Demand Summary\n\n"

# Define risk order to control order in markdown
risk_order = ["High", "Moderate", "Low"]

for risk_level in risk_order:
    landmarks = risk_groups.get(risk_level, [])
    
    markdown_text += f"#### {risk_level} Risk\n"
    
    if landmarks:
        for landmark in landmarks:
            markdown_text += f"- {landmark}\n"
    else:
        markdown_text += "- None\n"
    
    markdown_text += "\n"

# Show markdown result
print(markdown_text)
### Parking Demand Summary

#### High Risk
- Carlton Primary School
- Kensington Primary School

#### Moderate Risk
- The Royal Victorian Eye & Ear Hospital
- Kangan Batman Tafe
- University of Melbourne (VCA and Music)

#### Low Risk
- Carlton Gardens Primary School
- RMIT University
- University of Melbourne
- Royal Dental Hospital
- State Library Victoria


Public Transport Access Near High Parking Demand Landmarks¶

This analysis identifies public landmarks in Melbourne that have high parking demand and checks whether they are well-served by public transport.

What this step does:¶

  • Filters for landmarks classified as having High Parking Demand Risk.
  • For each of these landmarks:
    • Finds all bus stops within 400 meters.
    • Finds all tram stops within 400 meters.
  • Prints a summary that includes:
    • Landmark name
    • Number of nearby bus stops
    • Number of nearby tram stops

Key insight:¶

By checking how many public transport options are close to high-demand locations, we can:

  • Identify areas that lack alternative transport and may need more parking or better transit access.
  • Support urban planning decisions that reduce car dependency by improving bus/tram infrastructure.
  • Make data-driven recommendations to improve accessibility and reduce parking stress.

This helps ensure that busy landmarks are not just car-dependent but have practical and sustainable travel alternatives.

In [144]:
high_risk_landmarks = landmark_access_summary[landmark_access_summary['demand_risk'] == 'High']

# For each high-risk landmark, find nearby bus and tram stops
for _, landmark in high_risk_landmarks.iterrows():
    landmark_name = landmark['feature_name']
    landmark_location = (landmark['latitude'], landmark['longitude'])

    # Find nearby bus stops within 400m
    nearby_bus = filtered_bus_stop[filtered_bus_stop.apply(
        lambda x: geodesic(landmark_location, (x['latitude'], x['longitude'])).meters <= 400, axis=1
    )]

    # Find nearby tram stops within 400m
    nearby_tram = filtered_tram_stop[filtered_tram_stop.apply(
        lambda x: geodesic(landmark_location, (x['latitude'], x['longitude'])).meters <= 400, axis=1
    )]

    print(f"{landmark_name}")
    print(f"- Nearby Bus Stops: {len(nearby_bus)}")
    print(f"- Nearby Tram Stops: {len(nearby_tram)}\n")
Carlton Primary School
- Nearby Bus Stops: 17
- Nearby Tram Stops: 10

Kensington Primary School
- Nearby Bus Stops: 5
- Nearby Tram Stops: 0

Conclusion¶

Risk Level Landmarks
High - Carlton Primary School
- Kensington Primary School
Moderate - The Royal Victorian Eye & Ear Hospital
- Kangan Batman Tafe
- University of Melbourne (VCA and Music)
Low - Carlton Gardens Primary School
- RMIT University
- University of Melbourne
- Royal Dental Hospital

The parking demand analysis grouped healthcare and educational landmarks into three risk levels—high, moderate, and low—based on the ratio between adjusted parking spaces and peak pedestrian activity. Carlton Primary School and Kensington Primary School were classified as high-risk areas, indicating a potential parking shortage during peak times. These sites would benefit from either expanding public parking or encouraging the use of nearby public transportation to reduce car dependency.

Landmarks like The Royal Victorian Eye & Ear Hospital, Kangan Batman TAFE, and University of Melbourne (VCA and Music) fell into the moderate-risk category. While their parking conditions are not critical, these areas could face issues as demand increases. Measures such as promoting alternative transport options or monitoring peak usage patterns could help prevent future shortages.

In contrast, Carlton Gardens Primary School, RMIT University, University of Melbourne, and Royal Dental Hospital were assessed as low risk. These locations appear to have adequate parking relative to current pedestrian activity. No immediate interventions are necessary, but ongoing monitoring is recommended to ensure demand remains balanced with supply.

Public transport access plays a crucial role in supporting parking demand management. The analysis showed that high-risk landmarks vary significantly in their access to nearby bus and tram stops. For instance, Carlton Primary School is well-connected to both tram and bus networks, while Kensington Primary School has limited access. Strengthening public transport options near high-demand areas can provide effective alternatives to driving, easing pressure on parking infrastructure and enhancing overall accessibility.

Recommendations¶

  • Enhance Public Transport Access:
    For high-risk areas with limited public transport (e.g., Kensington Primary School), consider increasing bus service frequency or evaluating the feasibility of new tram stops to reduce dependence on car travel.

  • Promote Shared and Flexible Parking:
    Where parking spaces are shared by multiple landmarks, implement coordinated access policies and better signage to make the most efficient use of space.

  • Prioritise Pedestrian Data Expansion:
    Install more pedestrian sensors near healthcare and educational hubs to improve demand forecasting and support data-driven planning.

  • Dynamic Parking Management:
    Use peak-hour pedestrian patterns to guide pricing strategies, peak-time parking restrictions, or permit zones to balance usage.

  • Encourage Active and Public Transport Use:
    Provide safe walking and cycling paths from public transport nodes to high-demand destinations to support mode shift.

These insights and actions aim to promote a balanced, equitable, and sustainable approach to urban parking management in Melbourne.

In [ ]: